package antispam

import (
	"context"
	"fmt"
	"time"

	"go-main/library/cache/redis"
	"go-main/library/ecode"
	"go-main/library/log"

	"github.com/pkg/errors"
)

const (
	_prefixRate = "r_%s_%s_%d"
	// antispam
	_defSecond = 1
	_defN      = 1
)

// Antispam is a antispam instance.
type Antispam struct {
	redis *redis.Pool
	conf  *Config
}

// Config antispam config.
type Config struct {
	On     bool // switch on/off
	Second int  // every N second allow N requests.
	N      int  // one unit allow N requests.

	Redis *redis.Config
}

func (c *Config) validate() error {
	if c == nil {
		return errors.New("antispam: empty config")
	}
	if c.Second < _defSecond {
		return errors.New("antispam: invalid Second")
	}
	if c.N < _defN {
		return errors.New("antispam: invalid N")
	}
	return nil
}

// New new a antispam service.
func New(c *Config) (s *Antispam) {
	if err := c.validate(); err != nil {
		panic(err)
	}
	s = &Antispam{
		redis: redis.NewPool(c.Redis),
	}
	s.Reload(c)
	return s
}

// Reload reload antispam config.
func (s *Antispam) Reload(c *Config) {
	if err := c.validate(); err != nil {
		log.Errorf("Failed to reload antispam: %+v", err)
		return
	}
	s.conf = c
}

// Rate block requests by resource + visitor per n second.
func (s *Antispam) Rate(c context.Context, resource string, visitor string) (err error) {
	if !s.conf.On {
		return
	}
	second := s.conf.Second
	count := s.conf.N

	curSecond := int(time.Now().Unix())
	burst := curSecond - curSecond%second
	key := rateKey(resource, visitor, burst)
	return s.antispam(c, key, second, count)
}

func (s *Antispam) antispam(c context.Context, key string, interval, count int) error {
	conn := s.redis.Get(c)
	defer conn.Close()

	incred, err := redis.Int64(conn.Do("INCR", key))
	if err != nil {
		log.Errorf("antispam: redis incr %s failed!", key)
		return nil
	}

	if incred == 1 {
		conn.Do("EXPIRE", key, interval)
	}
	if incred > int64(count) {
		return ecode.LimitExceed
	}
	return nil
}

func rateKey(resource string, visitor string, burst int) string {
	return fmt.Sprintf(_prefixRate, resource, visitor, burst)
}
